<?PHP
/**
 * OpenStation
 *
 * @Application : Model.class.php
 * @Encode : 65001 Unicode ( UTF-8 NO BOM )
 * @Description : 数据模型基类
 * @Author : HK <hkyyang@gmail.com>
 */

class Model {

    // 数据库驱动实例
    protected $db = NULL;
    // 当前模型名称
    protected $modelName = NULL;
    // 当前模型的数据库名称
    protected $dbName = NULL;
    // 当前模型的数据表名
    protected $thisTableName = NULL;
    // 当前模型的完整数据表名
    protected $thisTrueTableName = NULL;
    // 缓存的数据表名称 => 完整数据表名
    protected $tableNames = array();
    // 数据模型错误信息
    protected $error = array();
    // 当前数据库的数据表信息缓存
    protected $tableCache = array();
    // 设置的数据
    protected $data = array();
    // 查询表达式数组
    protected $options = array();
    // 数据表后缀
    protected $tablePrefix = '';
    // 数据表前缀
    protected $tableSuffix = '';
    // 是否启动数据表字段缓存
    protected $fieldsCache = FALSE;
    // 是否启动数据表字段类型检查
    protected $fieldsCheck = FALSE;
    // 初始化时自动缓存表信息
    protected $initTableAutoCache = TRUE;

    // @ OVERRIDE 在子类扩展中继承覆盖
    // 字段映射,
    protected $_map = array();
    // 自动填充
    protected $_auto = array();
    // 自动启用事务
    protected $autoTransaction = FALSE;

    // 当前操作类型
    private $operation = 0;

    // 操作类型类常量
    const OPERATION_TYPE_C = 1; // Create
    const OPERATION_TYPE_U = 2; // Update
    const OPERATION_TYPE_R = 3; // Read
    const OPERATION_TYPE_D = 4; // Delete

    // 错误类型
    const ERROR_TYPE_MODEL = 1; // Model Error
    const ERROR_TYPE_DB = 2; // Db Error

    /**
     * 构造方法，初始化数据模型
     *
     * @access public
     * @param String : $name, 数据模型名称
     * @param Array | String : $config, 手动提供DB配置
     * @param Boolean : $autoTransaction, 自动事务
     * @return Mixed
     */
    public function __construct( $name = NULL, $config = NULL, $autoTransaction = FALSE ) {
        /* CALLBACK */
        // 初始化数据模型前的回调方法
        $this -> initial_before_callback( $name, $config );
        if( ! empty( $name ) ) {
            $this -> modelName = $name;
        } else {
            $this -> modelName = $this -> getModelName();
        }
        // 载入Db类
        if( ! class_exists( 'Db', FALSE ) ) {
            import( 'Lib.Db.Db' );
        }
        $this -> db = Db :: getInstance( $config );
        $this -> dbName = $this -> db -> dbName;
        // 表名统一前后缀
        $this -> tablePrefix = $this -> db -> tablePrefix;
        $this -> tableSuffix = $this -> db -> tableSuffix;
        // 开启字段缓存和验证
        $this -> fieldsCache = $this -> db -> fieldsCache;
        $this -> fieldsCheck = $this -> db -> fieldsCheck;
        $this -> autoTransaction = $autoTransaction;

        // 如果当前模型有名称，缓存当前模型表字段信息
        if( ! empty( $this -> modelName ) && $this -> initTableAutoCache ) {
            $this -> getTableInfo();
        }

        // 自动启用事务
        if( $this -> autoTransaction ) {
            $this -> transaction();
        }

        /* CALLBACK */
        // 初始化数据模型后的回调方法
        $this -> initial_after_callback();

    }

    /**
     * query方法，执行查询语句
     *
     * @access public
     * @param String : $sql
     * @return Mixed
     */
    public function query( $sql ) {
        $result = $this -> db -> query( $sql );
        if( $result === FALSE ) {
            $this -> throw_error( $this -> getDbError(), self :: ERROR_TYPE_DB );
        }
        return $result;
    }

    /**
     * execute方法，执行更新语句
     *
     * @access public
     * @param String : $sql
     * @return Mixed
     */
    public function execute( $sql ) {
        $result = $this -> db -> execute( $sql );
        if( $result === FALSE ) {
           $this -> throw_error( $this -> getDbError(), self :: ERROR_TYPE_DB );
        }
        return $result;
    }

    /**
     * regex方法，使用正规表达式验证数据
     *
     * @access public
     * @param String : $value, 验证的数据
     * @param String : $rule, 验证规则
     * @return Boolean
     */
    public function regex( $value, $rule ) {
        $validate = array(
            'require'=> '/.+/',
            'email' => '/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/',
            'url' => '/^http:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"\"])*$/',
            'currency' => '/^\d+(\.\d+)?$/',
            'number' => '/^\d+$/',
            'zip' => '/^[1-9]\d{5}$/',
            'integer' => '/^[-\+]?\d+$/',
            'double' => '/^[-\+]?\d+(\.\d+)?$/',
            'english' => '/^[A-Za-z]+$/',
        );
        // 检查是否有内置的正则表达式
        if( array_key_exists( $rule, $validate ) ) {
            $rule = $validate[ strtolower( $rule ) ];
        }
        return preg_match( $rule, $value ) === 1;
    }

    /**
     * getTableInfo方法，获取数据表的字段信息
     *
     * @access protected
     * @param String : $tbName, 数据表名称
     * @return Array
     */
    protected function getTableInfo( $tbName = NULL ) {

        // 没有传入表名,默认获取当前模型的表
        if( empty( $tbName ) ) {
            $this -> getTableName();
            $tbName = $this -> thisTableName;
        }

        $tbName = $this -> getShortTbName( $tbName );

        // 当表信息已经缓存，直接返回
        if( isset( $this -> tableCache[ $tbName ] ) ) {
            return $this -> tableCache[ $tbName ];
        }

        $tableInfo = FALSE;

        // 如果启用了表字段的文件缓存, 优先从文件获取
        if( $this -> fieldsCache && ! IS_CLI ) {
            $tableInfo = Cache :: file( 'DB_FIELD/' . $this -> dbName . '/' . $tbName );
        }

        if( ! $tableInfo ) {
            $info = $this -> db -> getFields( $this -> getTableName( $tbName ) );
            if( empty( $info ) ) {
                $this -> throw_error( Cache :: lang( 'TABLE_NOT_EXIST' ) . ' :"' . $tbName .'"', self :: ERROR_TYPE_MODEL );
            }
            $tableInfo = array(
                'fields' => array_keys( $info ),
                'autoinc' => FALSE
            );
            $type = array();
            foreach( $info as $k => $v ) {
                $type[ $k ] = $v[ 'type' ];
                if( $v[ 'primary' ] ) {
                    $tableInfo[ 'pk' ][] = $k;
                } else if( $v[ 'autoinc' ] ) {
                    $tableInfo[ 'autoinc' ] = TRUE;
                }
            }
            $tableInfo[ 'type' ] = $type;
            // 缓存到文件
            if( $this -> fieldsCache ) {
                Cache :: file( 'DB_FIELD/' . $this -> dbName . '/' . $tbName, $tableInfo );
            }
        }
        $this -> tableCache[ $tbName ] = $tableInfo;
        return $tableInfo;
    }

    /**
     * getModelName方法，获取当前模型名
     *
     * @access protected
     * @param $
     * @return String
     */
    protected function getModelName() {
        if( empty( $this -> modelName ) ) {
            return string_after_remove( get_class( $this ), 'Model' );
        }
        return $this -> modelName;
    }

    /**
     * getShortTbName方法，获取短表名( 去掉库名及"`" )
     *
     * @access protected
     * @param String : $tbName
     * @return String
     */
    protected function getShortTbName( $tbName ) {
        $tbName = str_replace( '`', '', $tbName );
        $temp = explode( '.', $tbName );
        return trim( array_pop( $temp ) );
    }

    /**
     * getTableName方法，获取完整的表名称
     *
     * @access protected
     * @param String : $tbName
     * @return String
     */
    protected function getTableName( $tbName = NULL ) {
        $tbName = $this -> getShortTbName( $tbName );
        // 统一表前缀
        $tableName = '';
        // 没有传入表名，获取当前模型的数据表
        if( empty( $tbName ) ) {
            if( ! empty( $this -> thisTrueTableName ) ) {
                return $this -> thisTrueTableName;
            } else if( ! empty( $this -> thisTableName ) ) {
                $tableName = string_prepend_replace( $this -> thisTableName, $this -> tablePrefix );
                $tableName = string_append_replace( $tableName, $this -> tableSuffix );
                $this -> thisTableName = $tableName;
            } else if( ! empty( $this -> modelName ) ) {
                $tableName = string_prepend_replace( named_style( $this -> modelName ), $this -> tablePrefix );
                $tableName = string_append_replace( $tableName, $this -> tableSuffix );
                $this -> thisTableName = $tableName;
            } else {
                throw_exception( Cache :: lang( 'CAN_NOT_GET_TABLE' ) );
            }
        } else {
            $tbName = string_append_replace( string_prepend_replace( $tbName, $this -> tablePrefix ), $this -> tableSuffix );
            // 传入了表名，但和默认的模型表名相同
            if( ! empty( $this -> thisTableName ) && $this -> thisTableName == $tbName ) {
                return $this -> getTableName( NULL );
            }
            // 尝试在表名缓存中搜索
            if( isset( $this -> tableNames[ $tbName ] ) ) {
                return $this -> tableNames[ $tbName ];
            } else {
                $tableName = $tbName;
            }
        }

        // 完整的表名前加上数据库名
        if( ! empty( $this -> dbName ) ) {
            $tableName = string_prepend_replace( $tableName, $this -> dbName . '.' );
        }
        // 当前的数据模型
        if( empty( $tbName ) ) {
            $this -> thisTrueTableName = $tableName;
        // 非当前模型的表，缓存到变量中
        } else {
            $this -> tableNames[ $tbName ] = $tableName;
        }
        return $tableName;
    }

    /**
     * parseByPk 方法，用主键来生成关键表达式
     *
     * @access private
     * @param String : $value
     * @param Boolean : $find, 只查询一条记录
     * @return String
     */
    private function parseByPk( $value, $find = FALSE ) {
        // 获取主键
        $pk = $this -> getPk( isset( $this -> options[ 'table' ] ) ? $this -> options[ 'table' ] : NULL );
        // 主键不存在
        if( is_null( $pk ) ) {
            return FALSE;
        // 联合主键查询( 查询一条 ), 要按主键顺序
        } else if( is_array( $pk ) ) {
            $where = '';
            $value = explode( '|', $value );
            foreach( $pk as $k => $v ) {
                $v = trim( $v );
                if( empty( $v ) ) {
                    continue;
                }
                if( isset( $value[ $k ] ) ) {
                    $where .= $this -> db -> addSpecialChar( $v ) . ' = \'' . $value[ $k ] . '\' AND ';
                }
            }
            return string_after_remove( $where, ' AND ' );
        // 单主键，可能是多查询
        } else {
            if( $find && strpos( $value, ',' ) ) {
                return $this -> db -> addSpecialChar( $pk ) . ' IN( ' . $value . ' )';
            }
            return $this -> db -> addSpecialChar( $pk ) . ' = \'' . $value . '\'';
        }
    }

    /**
     * select 方法，查询数据
     *
     * @access public
     * @param Array | String : $options
     * @return Mixed
     */
    public function select( $options = array() ) {
        // 标记操作类型
        $this -> operation = self :: OPERATION_TYPE_R;
        // 根据主键查询
        if( ( is_string( $options ) || is_numeric( $options ) ) && strlen( $options ) ) {
            $where = $this -> parseByPk( $options );
            if( FALSE === $where ) {
                $this -> setError( Cache :: lang( 'NO_PRIMARY_KEY' ) );
                return FALSE;
            } else {
                $options = array(
                    'where' => $where
                );
            }
        }
        $options = $this -> parseOptions( $options );
        $result = $this -> db -> select( $options );
        if( FALSE === $result ) {
            $this -> throw_error( $this -> getDbError(), self :: ERROR_TYPE_DB );
            return FALSE;
        } else if( empty( $result ) ) {
            return array();
        }
        /* CALLBACK */
        // 查询成功后回调函数
        $this -> select_callback( $result, $options );
        return $result;
    }

    /**
     * delete 方法，删除数据
     *
     * @access public
     * @param Array | String : $options
     * @return Mixed
     */
    public function delete( $options = array() ) {
        // 标记操作类型
        $this -> operation = self :: OPERATION_TYPE_D;
        // 根据主键删除
        if( ( is_string( $options ) || is_numeric( $options ) ) && strlen( $options ) ) {
            $where = $this -> parseByPk( $options );
            if( FALSE == $where ) {
                $this -> setError( Cache :: lang( 'MISSING_CONDITION' ) );
                return FALSE;
            } else {
                $options = array(
                    'where' => $where
                );
            }
        }
        $options = $this -> parseOptions( $options );
        $result = $this -> db -> delete( $options );
        if( $result !== FALSE ) {
            /* CALLBACK */
            // 删除成功后回调函数
            $this -> delete_callback( $options );
        } else {
            $this -> throw_error( $this -> getDbError(), self :: ERROR_TYPE_DB );
        }
        return $result;
    }

    /**
     * save 方法，保存数据( 自动 )
     *
     * @access public
     * @param Array : $data, 保存的数据
     * @param Array : $options
     * @return Mixed
     */
    public function save( $data = array(), $options = array() ) {

        $data = empty( $data ) ? $this -> data : $data;
        // 清空数据
        $this -> data = array();
        // 没有条件
        if( ! isset( $options[ 'where' ] ) ) {
            $options[ 'where' ] = $data;
        }
        $options = $this -> parseOptions( $options, $data );
        // 自动条件失败，不是更新操作
        if( $options[ 'where' ] == $data ) {
            unset( $options[ 'where' ] );
        }
        // 更新操作
        if( isset( $options[ 'where' ] ) ) {
            return $this -> update( $data, $options );
        // 写入操作
        } else {
            return $this -> replace( $data, $options );
        }
    }

    /**
     * update 方法，更新数据
     *
     * @access public
     * @param Array : $data, 更新的数据
     * @param Array : $options
     * @return Mixed
     */
     public function update( $data = array(), $options = array() ) {
        // 标记操作类型
        $this -> operation = self :: OPERATION_TYPE_U;
        $data = empty( $data ) ? $this -> data : $data;
        $options = $this -> parseOptions( $options, $data );
        // 清空数据
        $this -> data = array();
        /* CALLBACK */
        // 更新前的回调函数
        if( FALSE === $this -> before_update_callback( $data, $options ) ) {
            return FALSE;
        }

        // 字段映射
        $this -> fields_map( $data );

        // 检查数据
        if( $this -> checkData( $data, $options ) === FALSE ) {
            return FALSE;
        }

        $result = $this -> db -> update( $data, $options );
        if( $result !== FALSE ) {
            /* CALLBACK */
            // 更新后的回调函数
            $this -> after_update_callback( $data, $options );
        } else {
            $this -> throw_error( $this -> getDbError(), self :: ERROR_TYPE_DB );
        }
        return $result;
     }

    /**
     * data 方法，设置数据
     *
     * @access public
     * @param String | Array : $name, 字段名
     * @param String : $value, 值
     * @return Object
     */
     public function data( $name, $value = NULL ) {
        $data = array();
        if( ( is_string( $name ) || is_numeric( $name ) ) && strlen( $name ) ) {
            $data[ $name ] = $value;
        } else if( is_array( $name ) ) {
            $data = $name;
        }
        $this -> data = $data;
        return $this;
     }

    /**
     * set 方法，设置值
     *
     * @access public
     * @param String | Array : $name, 字段名
     * @param String : $value, 值
     * @return Object
     */
    public function set( $name, $value ) {
        if( ( is_string( $name ) || is_numeric( $name ) ) && strlen( $name ) ) {
            $this -> data[ $name ] = $value;
        } else if( is_array( $name ) ) {
            $this -> data = array_merge( $this -> data, $name );
        }
        return $this;
    }

    /**
     * get 方法，获取值
     *
     * @access public
     * @param String : $name, 字段名
     * @return Mixed
     */
    public function get( $name = NULL ) {
        if( is_null( $name ) ) {
            return $this -> data;
        } else if( isset( $this -> data[ $name ] ) ) {
            return $this -> data[ $name ];
        }
        return NULL;
    }

    /**
     * find 方法，查询一条数据
     *
     * @access public
     * @param Array | String : $options
     * @return Mixed
     */
    public function find( $options = array() ) {
        // 标记操作类型
        $this -> operation = self :: OPERATION_TYPE_R;
        // 根据主键查询
        if( is_string( $options ) || is_numeric( $options ) ) {
            $where = $this -> parseByPk( $options, TRUE );
            if( FALSE === $where ) {
                $this -> setError( Cache :: lang( 'NO_PRIMARY_KEY' ) );
                return FALSE;
            } else if( empty( $where ) ) {
                 $this -> setError( Cache :: lang( 'FIND::DATA_TYPE_ERROR' ) );
                return FALSE;
            } else {
                $options = array(
                    'where' => $where
                );
            }
        }
        $options[ 'limit' ] = 1;
        $options = $this -> parseOptions( $options );
        $result = $this -> db -> select( $options );
        if( FALSE === $result ) {
            $this -> throw_error( $this -> getDbError(), self :: ERROR_TYPE_DB );
            return FALSE;
        } else if( empty( $result ) ) {
            return array();
        }
        $this -> data = current( $result );
        /* CALLBACK */
        // 查询成功后回调函数
        $this -> find_callback( $this -> data, $options );
        return $this -> data;
    }

    /**
     * insert 方法，写入数据
     *
     * @access public
     * @param Array : $data
     * @param Array | String : $options
     * @return Mixed
     */
    public function insert( $data = array(), $options = array() ) {
        // 标记操作类型
        $this -> operation = self :: OPERATION_TYPE_C;
        $options = $this -> parseOptions( $options );
        $data = empty( $data ) ? $this -> data : $data;
        // 清空数据
        $this -> data = array();
        /* CALLBACK */
        // 写入前的回调函数
        if( FALSE === $this -> before_insert_callback( $data, $options ) ) {
            return FALSE;
        }

        // 字段映射
        $this -> fields_map( $data );

        // 检查数据
        if( $this -> checkData( $data, $options ) === FALSE ) {
            return FALSE;
        }

        $result = $this -> db -> write( $data, $options );
        if( $result !== FALSE ) {
            /* CALLBACK */
            // 写入后的回调函数
            $this -> after_insert_callback( $data, $options );
        } else {
            $this -> throw_error( $this -> getDbError(), slef :: ERROR_TYPE_DB );
        }
        return $result;
    }

    /**
     * replace 方法，覆盖写入数据
     *
     * @access public
     * @param Array : $data
     * @param Array | String : $options
     * @return Mixed
     */
    public function replace( $data = array(), $options = array() ) {
        $this -> options[ 'replace' ] = TRUE;
        return $this -> insert( $data, $options );
    }


    /**
     * fields_map 方法，字段映射
     *
     * @access protected
     * @param Array : & $data, 数据
     * @return Void
     */
    protected function fields_map( & $data = array() ) {
        if( ! empty( $this -> _map ) && is_array( $this -> _map ) ) {
            foreach( $this -> _map as $k => $v ) {
                if( isset( $data[ $k ] ) ) {
                    $data[ $v ] = $data[ $k ];
                    unset( $data[ $k ] );
                }
            }
        }
    }

    /**
     * transaction 方法，启动事务
     *
     * @access public
     * @param $
     * @return Boolean
     */
    public function transaction() {
        $this -> db -> commit();
        $this -> db -> transaction();
        return TRUE;
    }

    /**
     * rollback 方法，事务回滚
     *
     * @access public
     * @param $
     * @return Boolean
     */
    public function rollback() {
        if( $this -> db -> rollback() && $this -> autoTransaction ) {
            $this -> db -> transaction();
        }
        return TRUE;
    }

    /**
     * commit 方法，提交事务
     *
     * @access public
     * @param $
     * @return Mixed
     */
    public function commit() {
        if( $this -> db -> commit() && $this -> autoTransaction ) {
            $this -> db -> transaction();
        }
        return TRUE;
    }

    /**
     * getField 方法，根据字段名查询数据，并整合到字符串中
     *
     * @access public
     * @param String : $field
     * @param String : $key, 作为键名的字段，并且值数据中不再有此字段的值。如果为空，默认数字索引，
     * @param String : $separated, 数据分隔符
     * @param String : $isnull, 空数据替换成字符串
     * @return Mixed
     */
    public function getField( $field = NULL, $key = NULL, $separated = '|', $isnull = '' ) {
        if( ! empty( $field ) ) {
            $this -> options[ 'field' ] = $field;
        }
        $options = $this -> parseOptions();
        // 统计查询
        if( str_replace( array( 'COUNT', 'SUM', 'MIN', 'MAX', 'AVG' ), '', $field ) != $field ) {
            $options[ 'limit' ] = 1;
            $result = $this -> db -> select( $options );
            if( $result !== FALSE ) {
                $result = current( $result );
                $ret = array();
                if( strpos( $field, ',' ) !== FALSE ) {
                    $method = strtolower( substr( $field, 0, strpos( $field, '(' ) ) );
                    foreach( $result as $k => $v ) {
                        $ret[ string_after_remove( $k, '_' . $method ) ] = $v;
                    }
                    return $ret;
                }
                return array_shift( $result );
            }
        // 多字段查询
        } else {
            $key = NULL;
            if( ! empty( $field ) ) {
                if( strpos( $field, ',' ) ) {
                    $field = explode( ',', $field );
                } else {
                    $field = array( $field );
                }
                $key = in_array( $key, $field, TRUE ) ? $key : NULL;
            }
            $resultSet = $this -> db -> select( $options );
            if( ! empty( $resultSet ) ) {
                $cols = array();
                foreach( $resultSet as $i => $result ) {
                    if( is_null( $key ) ) {
                        $name = $i;
                    } else {
                        $name = $result[ $key ];
                    }
                    $cols[ $name ] = '';
                    foreach( $result as $k => $v ) {
                        if( ! is_null( $key ) && $k == $key ) {
                            continue;
                        }
                        // 空数据替换
                        if( ! empty( $isnull ) && ! strlen( $v ) ) {
                            $v = $isnull;
                        }
                        $cols[ $name ] .= $v . $separated;
                    }
                    $cols[ $name ] = string_after_remove( $cols[ $name ], $separated );
                }
                return $cols;
            }
        }
        return NULL;
    }

    /**
     * getPk 方法，获取数据表主键
     *
     * @access protected
     * @param String : $tbName, 指定一个数据表名
     * @return Array | String
     */
    protected function getPk( $tbName = NULL ) {

        // 默认获取当前表信息
        $table_info = $this -> getTableInfo( $tbName );

        if( isset( $table_info[ 'pk' ] ) ) {
            // String 单一主键
            if( count( $table_info[ 'pk' ] ) == 1 ) {
                return array_shift( $table_info[ 'pk' ] );
            // Array 联合主键
            } else if( count( $table_info[ 'pk' ] ) > 1 ) {
                return $table_info[ 'pk' ];
            }
        }
        return NULL;
    }

    /**
     * parseOptions 方法，解析参数
     *
     * @access protected
     * @param Array : $options
     * @param Array : $data
     * @return Array
     */
    protected function parseOptions( $options = array(), $data = array() ) {
        if( is_array( $options ) ) {
            $options = array_merge( $this -> options, $options );
        }
        $this -> options = $options;
        // Table 解析
        if( isset( $options[ 'table' ] ) && ! empty( $options[ 'table' ] ) ) {
            if( is_array( $options[ 'table' ] ) ) {
                Cache :: set( 'PARSE_TABLE_TEMP', $options[ 'table' ] );
                unset( $options[ 'table' ] );
                foreach( Cache :: get( 'PARSE_TABLE_TEMP' ) as $k => $v ) {
                    $options[ 'table' ][ $k ] = $this -> getTableName( $v );
                }
                Cache :: out( 'PARSE_TABLE_TEMP' );
            } else {
                $options[ 'table' ] = $this -> getTableName( $options[ 'table' ] );
            }
        } else {
            $options[ 'table' ] = $this -> getTableName( NULL );
        }

        $table_info = $this -> getTableInfo( $options[ 'table' ] );

        // 去除字段, 优先于field操作
        if( isset( $options[ 'notfield' ] ) && isset( $table_info[ 'fields' ] ) && ! empty( $table_info[ 'fields' ] ) ) {
            $notfield = explode( ',', $options[ 'notfield' ] );
            $options[ 'field' ] = $table_info[ 'fields' ];
            foreach( $notfield as $not ) {
                $is_field = array_search( trim( $not ), $table_info[ 'fields' ] );
                if( $is_field > -1 ) {
                    unset( $options[ 'field' ][ $is_field ] );
                }
            }
            $options[ 'field' ] = implode( ',', $options[ 'field' ] );
        }

        // 数组条件( 数据 ), 以数据的主键构造条件
        if( isset( $options[ 'where' ] ) && is_array( $options[ 'where' ] ) && ! empty( $options[ 'where' ] ) ) {
            $pk = $this -> getPk( $options[ 'table' ] );
            if( ! empty( $pk ) ) {
                $auto_u = TRUE;
                $pkValue = '';
                // 联合主键
                if( is_array( $pk ) ) {
                    // 检查是否存在主键数据
                    foreach( $pk as $p ) {
                        if( ! array_key_exists( $p, $options[ 'where' ] ) ) {
                            $auto_u = FALSE;
                            break;
                        } else {
                            $pkValue .= $options[ 'where' ][ $p ] . '|';
                        }
                    }
                } else if( ! array_key_exists( $pk, $options[ 'where' ] ) ) {
                    $auto_u = FALSE;
                } else {
                    $pkValue .= $options[ 'where' ][ $pk ];
                }
                $pkValue = string_after_remove( $pkValue, '|' );
                // 必要条件达成
                if( $auto_u === TRUE ) {
                    $options[ 'where' ] = $this -> parseByPk( $pkValue );
                }
            }
        // 自动条件机能, 以数据的主键值为条件( U 操作 )
        } else if( $this -> operation == self :: OPERATION_TYPE_U && ( ! isset( $options[ 'where' ] ) || empty( $options[ 'where' ] ) ) && ! empty( $data ) ) {
            $pk = $this -> getPk( $options[ 'table' ] );
            // 存在主键时
            if( ! empty( $pk ) ) {
                $auto_u = TRUE;
                $pkValue = '';
                // 联合主键
                if( is_array( $pk ) ) {
                    // 检查是否存在主键数据
                    foreach( $pk as $p ) {
                        if( ! array_key_exists( $p, $data ) ) {
                            $auto_u = FALSE;
                            break;
                        } else {
                            $pkValue .= $data[ $p ] . '|';
                        }
                    }
                // 单主键
                } else if( ! array_key_exists( $pk, $data ) ) {
                    $auto_u = FALSE;
                } else {
                    $pkValue .= $data[ $pk ];
                }
                $pkValue = string_after_remove( $pkValue, '|' );
                // 必要条件达成
                if( $auto_u === TRUE ) {
                    $options[ 'where' ] = $this -> parseByPk( $pkValue );
                }
            }
        }

        $this -> options = array();
        /* CALLBACK */
        // 参数解析后回调方法
        $this -> parseOptions_callback( $options );
        return $options;
    }


    /**
     * checkData 方法，验证数据
     *
     * @access protected
     * @param Array : & $data
     * @param Array : $options
     * @return Boolean
     */
    protected function checkData( & $data, $options ) {

        if( is_object( $data ) ) {
            $data = get_object_vars( $data );
        }

        $backupData = $data;

        $table_info = $this -> getTableInfo( $options[ 'table' ] );

        // 过滤 不存在的字段值 或 非标量但也不是数组
        if( is_array( $data ) && is_array( $table_info ) ) {
            foreach( $data as $k => $v ) {
                if( ! in_array( $k, $table_info[ 'fields' ], TRUE ) || ( ! is_scalar( $v ) && ! is_array( $v ) ) ) {
                    unset( $data[ $k ] );
                // 字段类型验证
                } else if( $this -> fieldsCheck && is_scalar( $v ) && isset( $table_info[ 'type' ] ) ) {
                    $type = strtolower( $table_info[ 'type' ][ $k ] );
                    if( strpos( $type, 'int' ) !== FALSE ) {
                        $data[ $k ] = intval( $v );
                    } else if( strpos( $type, 'float' ) !== FALSE || strpos( $type, 'double' ) !== FALSE ) {
                        $data[ $k ] = floatval( $v );
                    }
                }
            }
        }

        // 自动填充
        foreach( $this -> _auto as $k => $v ) {
            switch( $k{ 0 } ):
                // Create
                case '#' :
                    if( $this -> operation == self :: OPERATION_TYPE_C ) {
                        $k = substr( $k, 1 );
                    } else {
                        continue( 2 );
                    }
                break;
                // Update
                case '@' :
                    if( $this -> operation == self :: OPERATION_TYPE_U ) {
                        $k = substr( $k, 1 );
                    } else {
                        continue( 2 );
                    }
                break;
            endswitch;

            if( ! array_key_exists( $k, $data ) ) {
                // 应用函数
                if( is_array( $v ) ) {
                    if( count( $v ) > 1 ) {
                        $value = call_user_func_array( $v[ 0 ], $v[ 1 ] );
                    } else {
                        $value = call_user_func( current( $v ) );
                    }
                // 填充字符串
                } else {
                    $value = $v;
                }
                // 只填充标量值
                if( is_scalar( $value ) ) {
                    $data[ $k ] = $value;
                }
            }
        }

        /* CALLBACK */
        // 数据验证后的回调方法
        $this -> checkData_callback( $data );

        // 过滤完之后是空的
        if( empty( $data ) || ! is_array( $data ) ) {
            $backupData = var_export( $backupData, TRUE );
            $this -> setError( Cache :: lang( 'NULL::DATA_TYPE_ERROR ' ) . $backupData );
            return FALSE;
        }
        return TRUE;
    }


    /*!
     * -=========================== Override ===========================-
     */

    /**
     * 初始化数据模型前的回调方法
     *
     * @override
     * @access protected
     * @param String : & $name, 模型名称
     * @param Array | String : & $config, 配置
     * @return void
     */
    protected function initial_before_callback( & $name, & $config ) {}

    /**
     * 初始化数据模型后的回调方法
     *
     * @override
     * @access protected
     * @param $
     * @return void
     */
    protected function initial_after_callback() {}

    /**
     * 检查数据后的回调方法
     *
     * @override
     * @access protected
     * @param Array : & $data, 数据
     * @return void
     */
    protected function checkData_callback( & $data ) {}

    /**
     * 插入数据前的回调方法, 返回一个布尔值决定是否继续执行
     *
     * @override
     * @access protected
     * @param Array : & $data
     * @param Array : $options
     * @return Boolean | Void
     */
    protected function before_insert_callback( & $data, $options ) {}

    /**
     * 插入数据后的回调方法
     *
     * @override
     * @access protected
     * @param Array : $data
     * @param Array : $options
     * @return void
     */
    protected function after_insert_callback( $data, $options) {}

    /**
     * 更新数据前的回调方法, 返回一个布尔值决定是否继续执行
     *
     * @override
     * @access protected
     * @param Array : & $data
     * @param Array : $options
     * @return Boolean
     */
    protected function before_update_callback( & $data, $options ) {}

    /**
     * 更新数据后的回调方法
     *
     * @override
     * @access protected
     * @param Array : $data
     * @param Array : $options
     * @return void
     */
    protected function after_update_callback( $data, $options) {}

    /**
     * 删除成功后的回调方法
     *
     * @override
     * @access protected
     * @param Array : $options
     * @return void
     */
    protected function delete_callback( $options ) {}

    /**
     * 查询成功后的回调方法
     *
     * @override
     * @access protected
     * @param Resource : & $result
     * @param Array : $options
     * @return void
     */
    protected function select_callback( & $result, $options ) {}

    /**
     * 查询成功后的回调方法
     *
     * @override
     * @access protected
     * @param Array : $data
     * @param Array : $options
     * @return void
     */
    protected function find_callback( $data, $options ) {}

    /**
     * 表达式过滤后的回调方法
     *
     * @override
     * @access protected
     * @param Array : & $options
     * @return void
     */
    protected function parseOptions_callback( & $options ) {}


    /**
     * clearTableCache 方法，清除表信息文件缓存
     * 在表结构更改时，手动调用此方法刷新缓存
     * 仅用于表结构经常更改的调式模式
     * 实例化新模型时通过参数调用
     * 调试模式始终启用
     *
     * @access private
     * @return void
     */
    private function clearTableCache() {
        $cache_dir = Cache :: get( 'APP_PATH.CACHE' ) . 'DB_FIELD' . DS . $this -> dbName . DS;
        if( is_dir( $cache_dir ) ) {
            rm( $cache_dir );
        }
    }

    /**
     * getDbError 方法，获取数据库错误
     *
     * @access public
     * @return void
     */
    public function getDbError() {
        return $this -> db -> getDbError();
    }

    /**
     * throw_error 方法, 抛出错误
     *
     * @access public
     * @param String : $message
     * @param Integer : $errno
     * @return void
     */
    private function throw_error( $message, $errno ) {
        $e = array( 'message' => $message );
        if( $errno == 1 ) {
            $e[ 'type' ] = 'MODEL ERROR';
            $e[ 'sql' ] = '';
        } else {
            $e[ 'type' ] = 'DB SQL ERROR';
            $e[ 'sql' ] = $this -> getQuerySql();
        }
        Log :: sql_error( $e[ 'message' ], $e[ 'sql' ] );
        if( IS_CLI ) {
            echo $e[ 'type' ], "\t", $e[ 'message' ], "\t", $e[ 'sql' ], "\n";
            if( $errno == 2 ) {
                exit;
            }
        // AJAX 返回
        } else if( Cache :: get( 'HTTP.AJAX' ) ) {
                $e[ 'error' ] = 1;
                if( ! Cache :: get( '@.ERROR.DEBUG' ) ) {
                    unset( $e[ 'sql' ] );
                }
                echo json_encode( $e );
                exit;
        // 开启了调试模式
        } else if( Cache :: get( '@.ERROR.DEBUG' ) ) {
            switch( $errno ) {
                // 入栈, 把模型错误归累普通错误
                case 1 : Cache :: append( 'SERVER.ERROR', $e ); break;
                case 2 : include Cache :: get( 'OPENSTATION.ERROR.DATABASE' );
            }
        }
    }

    /**
     * setError 方法，设置数据模型错误
     *
     * @access public
     * @param String : $error
     * @return void
     */
    protected function setError( $error ) {
        $this -> throw_error( $error, self :: ERROR_TYPE_MODEL );
        $error .= ' ' . $this -> getQuerySql();
        $this -> error[] = $error;
    }

    /**
     * getError 方法，获取数据模型错误
     *
     * @access public
     * @return Array
     */
    public function getError() {
        return count( $this -> error ) ? $this -> error : NULL;
    }

    /**
     * getQuerySql 方法，获取最后执行的语句
     *
     * @access public
     * @return void
     */
    public function getQuerySql() {
        return $this -> db -> getQuerySql();
    }

    /**
     * getLastInsertID 方法，获取最后一次插入的ID
     *
     * @access public
     * @return void
     */
    public function getLastInsertID() {
        return $this -> db -> getLastInsertID();
    }

    /**
     * getNumRows 方法，获取影响的行数
     *
     * @access public
     * @return void
     */
    public function getNumRows() {
        return $this -> db -> getNumRows();
    }

    /**
     * close 方法，关闭数据库连接
     *
     * @access public
     * @return Boolean
     */
    public function close() {
        return $this -> db -> closeDB();
    }

    /**
     * __set 魔术方法, 设置数据的值
     *
     * @access public
     * @param String : $name
     * @param * : $value
     * @return void
     */
    public function __set( $name, $value ) {
        $this->data[$name] = $value;
    }

    /**
     * __get 魔术方法, 获取数据的值
     *
     * @access public
     * @param String : $name
     * @return Mixed
     */
    public function __get( $name ) {
        return isset( $this -> data[ $name ] ) ? $this -> data[ $name ] : NULL;
    }

    /**
     * __isset 魔术方法, 检查一个值是否被设置
     *
     * @access public
     * @param String : $name
     * @return void
     */
    public function __isset( $name ) {
        isset( $this -> data[ $name ] );
    }

    /**
     * __unset 魔术方法, 销毁一个数据值
     *
     * @access public
     * @param String : $name
     * @return void
     */
    public function __unset( $name ) {
        unset( $this -> data[ $name ] );
    }

    /**
     * __call魔术方法, 链式操作实现
     *
     * @access public
     * @param String : $method
     * @param Array : $args
     * @return Object | Array
     */
    public function __call( $method, $args ) {
        $method = strtolower( $method );
        if( in_array( $method, array( 'field', 'notfield', 'table', 'where', 'order', 'limit', 'page', 'alias', 'having', 'group', 'lock', 'distinct' ), TRUE ) ) {
            $this -> options[ $method ] = $args[ 0 ];
            return $this;
        } else if( in_array( $method, array( 'join', 'left', 'right' ), TRUE ) ) {
            $args[ 0 ] = str_replace( '%THIS_TABLE%', $this -> getTableName( NULL ), $args[ 0 ] );
            switch( $method ) {
                case 'left' : $this -> options[ 'join' ][] = 'LEFT JOIN ' . $args[ 0 ]; break;
                case 'right' : $this -> options[ 'join' ][] = 'RIGHT JOIN ' . $args[ 0 ]; break;
                default : $this -> options[ 'join' ][] = $args[ 0 ];
            }
            return $this;
        // 统计查询
        } else if( in_array( $method, array( 'count', 'sum', 'min', 'max', 'avg' ), TRUE ) ) {
            $fields = isset( $args[ 0 ] ) ? $args[ 0 ] : '*';
            $alias = 'os_' . $method;
            if( strpos( $fields, ',' ) !== FALSE ) {
                $fieldsArr = explode( ',', $fields );
                $fieldsArr = array_map( 'trim', $fieldsArr );
                $sql = '';
                foreach( $fieldsArr as $f ) {
                    $sql .= strtoupper( $method ) . '(' . $f . ') AS ' . $f . '_' . strtolower( $method ) . ',';
                }
                $sql = string_after_remove( $sql, ',' );
                return $this -> getField( $sql );
            }
            return $this -> getField( strtoupper( $method ) . '(' . $fields . ') AS ' .  '_' . strtolower( $method ) . '_' );
        // 根据某一个键名查询记录
        } else if( substr( $method, 0, 5 ) == 'getby' ) {
            $fields = named_style( substr( $method, 5 ) );
            $options[ 'where' ] = $fields . "='{$args[ 0 ]}'";
            return $this -> select( $options );
        // 获取某条记录一的一个键名
        } else if( substr( $method, 0, 6 ) == 'findby' ) {
            $field = named_style( substr( $method, 6 ) );
            $tmp = $this -> field( $field ) -> find();
            return isset( $tmp[ $field ] ) ? $tmp[ $field ] : NULL;
        } else {
            throw_exception( Cache :: lang( 'METHOD_NOT_EXIST' ) . ': ' . $method . '( ' . implode( ', ', $args ) . ' )' );
        }
        return FALSE;
    }

}
?>